﻿//-----------------------------------------------------------------------
// <copyright file="MathUtility.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Common.Math
{
    using System;

    /// <summary>
    /// Math utility class.
    /// </summary>
    public static class MathUtility
    {
        #region Constants - Public

        /// <summary>
        /// Floating point epsilon value.
        /// </summary>
        public const float Epsilon = 0.00001f;

        #endregion

        #region Constants - Private

        /// <summary>
        /// Degrees to radians multiplier.
        /// </summary>
        private const double DegToRadMult = System.Math.PI / 180.0f;

        #endregion

        #region Methods - Public

        /// <summary>
        /// Clamp the passed value to the provided inclusive range.
        /// </summary>
        /// <param name="value">Value to clamp.</param>
        /// <param name="min">Min value.</param>
        /// <param name="max">Max value.</param>
        /// <returns>Returns the value clamped to the min / max range.</returns>
        public static float Clamp(float value, float min, float max)
        {
            if (value < min)
            {
                return min;
            }

            if (value > max)
            {
                return max;
            }

            return value;
        }

        /// <summary>
        /// Clamp the passed value to a unit value range of 0.0f to 1.0f.
        /// </summary>
        /// <param name="value">Value to clamp.</param>
        /// <returns>Returns the value clamped to a unit range.</returns>
        public static float ClampUnit(float value)
        {
            return Clamp(value, 0.0f, 1.0f);
        }

        /// <summary>
        /// Clamped the passed value against the provided minimum value.
        /// </summary>
        /// <param name="value">Current value.</param>
        /// <param name="min">Minimum value.</param>
        /// <returns>Returns the clamped value if it is less than min or the input value if it is not.</returns>
        public static float ClampMin(float value, float min)
        {
            return value < min ? min : value;
        }

        /// <summary>
        /// Convert degrees to radians.
        /// </summary>
        /// <param name="deg">Amount in degrees to convert.</param>
        /// <returns>Returns the passed amount in degrees as radians.</returns>
        public static float DegreesToRadians(float deg)
        {
            return (float)(deg * DegToRadMult);
        }

        /// <summary>
        /// Calculates the square of the passed value.
        /// </summary>
        /// <param name="value">Value to square.</param>
        /// <returns>Returns the square of the passed value.</returns>
        public static float Square(float value)
        {
            return value * value;
        }

        /// <summary>
        /// Snap the passed value to a multiple of snapSize.
        /// </summary>
        /// <param name="value">Value to snap.</param>
        /// <param name="snapSize">Increment to snap to.</param>
        /// <returns>Returns the value snapped to a multiple of snapSize.</returns>
        public static float SnapValue(float value, float snapSize)
        {
            if (snapSize <= 0.0f)
            {
                throw new ArgumentException("Snap size must be > 0.0f");
            }

            int numTimes = (int)(value / snapSize);
            float newValue = numTimes * snapSize;
            float remainder = System.Math.Abs(value - newValue);

            // If we are over half way to the next amount, jump to it.
            if (remainder > (snapSize * 0.5f))
            {
                newValue += System.Math.Sign(value) * snapSize;
            }

            return newValue;
        }

        /// <summary>
        /// Snap a vector to a grid that is snapSize in width and height.
        /// </summary>
        /// <param name="vec">Vector to snap.</param>
        /// <param name="snapSize">Increment to snap to.</param>
        /// <returns>Returns the input point snapped to a grid of snapSize.</returns>
        public static Vector2D SnapVector(Vector2D vec, float snapSize)
        {
            float newX = SnapValue(vec.x, snapSize);
            float newY = SnapValue(vec.y, snapSize);

            return new Vector2D(newX, newY);
        }

        /// <summary>
        /// Linear interpolation between a and b.
        /// </summary>
        /// <param name="amount">Interpolation amount.</param>
        /// <param name="a">Unit amount 0.0f value.</param>
        /// <param name="b">Unit amount 1.0f value.</param>
        /// <returns>Returns the lerped value.</returns>
        public static float Lerp(float amount, float a, float b)
        {
            return a + (amount * (b - a));
        }

        #endregion
    }
}
